import type {
	ArrayStyle,
	ObjectStyle,
	SerializerOptions,
} from './pathSerializer';

export type BodySerializer = (body: any) => any;

export type QuerySerializer = (query: Record<string, unknown>) => string;

export interface QuerySerializerOptions {
	allowReserved?: boolean;
	array?: SerializerOptions<ArrayStyle>;
	object?: SerializerOptions<ObjectStyle>;
}

const serializeFormDataPair = (
	data: FormData,
	key: string,
	value: unknown,
): void => {
	if (typeof value === 'string' || value instanceof Blob) {
		data.append(key, value);
	} else {
		data.append(key, JSON.stringify(value));
	}
};

const serializeUrlSearchParamsPair = (
	data: URLSearchParams,
	key: string,
	value: unknown,
): void => {
	if (typeof value === 'string') {
		data.append(key, value);
	} else {
		data.append(key, JSON.stringify(value));
	}
};

export const formDataBodySerializer = {
	bodySerializer: <T extends Array<Record<string, any>> | Record<string, any>>(
		body: T,
	): FormData => {
		const data = new FormData();

		Object.entries(body).forEach(([key, value]) => {
			if (value === undefined || value === null) {
				return;
			}
			if (Array.isArray(value)) {
				value.forEach((v) => serializeFormDataPair(data, key, v));
			} else {
				serializeFormDataPair(data, key, value);
			}
		});

		return data;
	},
};

export const jsonBodySerializer = {
	bodySerializer: <T>(body: T): string =>
		JSON.stringify(body, (_key, value) =>
			typeof value === 'bigint' ? value.toString() : value,
		),
};

export const urlSearchParamsBodySerializer = {
	bodySerializer: <T extends Array<Record<string, any>> | Record<string, any>>(
		body: T,
	): string => {
		const data = new URLSearchParams();

		Object.entries(body).forEach(([key, value]) => {
			if (value === undefined || value === null) {
				return;
			}
			if (Array.isArray(value)) {
				value.forEach((v) => serializeUrlSearchParamsPair(data, key, v));
			} else {
				serializeUrlSearchParamsPair(data, key, value);
			}
		});

		return data.toString();
	},
};
